package org.store.dao;

import org.springframework.dao.DataAccessException;
import org.springframework.jdbc.core.BatchPreparedStatementSetter;
import org.springframework.transaction.annotation.Transactional;
import org.store.domain.Buyer;
import org.store.domain.Order;
import org.store.domain.Payment;
import org.store.domain.Product;

import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

public class OrderDao extends AbstractDao<Order> {

    private BuyerDao buyerDao;
    private PaymentDao paymentDao;

    @Override
    @Transactional
    public Order getById(long id) throws DaoException {
        Map<String, Object> map = jdbcTemplate.queryForMap("select buyer_id, payment_id, order_sum from orders where order_id = ?", id);
        if (map == null || map.isEmpty())
            return null;

        Order order = new Order(
                paymentDao.getById((Long) map.get("payment_id")),
                buyerDao.getById((Long) map.get("buyer_id")),
                (Long) map.get("order_sum")
        );

        List<Map<String, Object>> rows = jdbcTemplate.queryForList("select p.title, p.price, op.amount from " +
                "order_product op join product p on (op.product_id = p.product_id) " +
                "where op.order_id = ?", id);

        for (Map<String, Object> each : rows) {
            order.getProducts().put(
                    new Product(
                            (String) each.get("title"),
                            (Long) each.get("price"),
                            (Integer) each.get("amount"),
                            (Integer) each.get("image_id"),
                            (Long) each.get("category_id")),//TODO:FIX
                    (Integer) each.get("amount")
            );
        }

        return order;
    }

    @Override
    @Transactional
    public Order save(final Order order) throws DaoException {
        if (order == null) {
            throw new IllegalArgumentException("Cannot be null");
        }
        Payment payment = order.getPayment();
        if (payment != null && payment.getId() == null) {
            payment = paymentDao.save(payment);
        }

        Buyer buyer = order.getBuyer();
        if (buyer != null && buyer.getId() == null) {
            buyer = buyerDao.save(buyer);
        }
        try {
            final long orderId = jdbcTemplate.queryForLong("insert into orders (buyer_id, payment_id, order_sum) values(?,?,?) returning order_id",
                    buyer == null ? null : buyer.getId(),
                    payment == null ? null : payment.getId(),
                    order.getSum());

            saveProducts(order, orderId);

            Order newOrder = new Order(payment, buyer, order.getSum());
            newOrder.getProducts().putAll(order.getProducts());
            newOrder.setId(orderId);
            return newOrder;
        } catch (DataAccessException e) {
            throw new DaoException("Cannot save order with id = " + order.getId(), e);
        }
    }

    @Override
    public boolean delete(Order order) throws DaoException {
        try {
            int updated = jdbcTemplate.update("delete from orders where order_id = ?", order.getId());
            return updated > 0;
        } catch (DataAccessException e) {
            throw new DaoException("Cannot delete order with id = " + order.getId(), e);
        }
    }

    @Override
    public Order saveOrUpdate(Order order) throws DaoException {
        if (order.getId() == null) {
            return save(order);
        }
        try {
            jdbcTemplate.update("update payment SET buyer_id = ?, payment_id = ? where order_id = ?", order.getBuyer().getId(),
                    order.getPayment().getId(), order.getId());
            return order;
        } catch (DataAccessException e) {
            throw new DaoException("Cannot update order with id = " + order.getId(), e);
        }
    }

    @Override
    @Transactional
    public List<Order> saveAll(final List<Order> orders) throws DaoException {
        if (orders == null)
            throw new IllegalArgumentException("Array cannot be null");
        for (Order order : orders) {
            save(order);
        }
        return orders;
    }

    @Override
    public boolean isExists(long id) throws DaoException {
        try {
            return jdbcTemplate.queryForLong("select count(*) from orders where order_id = ?", id) > 0;
        } catch (DataAccessException e) {
            throw new DaoException("Cannot get order with id = " + id, e);
        }
    }

    private void saveProducts(final Order order, final long orderId) {
        final List<BatchProduct> products = BatchProduct.wrap(order.getProducts());
        jdbcTemplate.batchUpdate("insert into order_product (order_id, product_id, amount) values (?,?,?)", new BatchPreparedStatementSetter() {
            @Override
            public void setValues(PreparedStatement ps, int i) throws SQLException {
                ps.setLong(1, orderId);
                ps.setLong(2, products.get(i).getProductId());
                ps.setLong(3, products.get(i).getAmount());
            }

            @Override
            public int getBatchSize() {
                return order.getProducts().size();
            }
        });
    }

    public void setBuyerDao(BuyerDao buyerDao) {
        this.buyerDao = buyerDao;
    }

    public void setPaymentDao(PaymentDao paymentDao) {
        this.paymentDao = paymentDao;
    }

    private static class BatchProduct {
        private final int amount;
        private final long productId;

        private BatchProduct(int amount, long productId) {
            this.amount = amount;
            this.productId = productId;
        }

        private int getAmount() {
            return amount;
        }

        private long getProductId() {
            return productId;
        }

        public static List<BatchProduct> wrap(Map<Product, Integer> map) {
            List<BatchProduct> res = new ArrayList<>(map.size());
            for (Map.Entry<Product, Integer> each : map.entrySet()) {
                Long id = each.getKey().getId();
                if (id == null) {
                    throw new IllegalStateException("Trying to save order with not existed product (without id)");
                }
                res.add(new BatchProduct(each.getValue(), id));
            }
            return res;
        }
    }
}
